﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DataExtractionLanguage
{
    public class SyntaxAnalyzer
    {
        private readonly ILexical lexer;
        private readonly Report report;

        public List<SelectStatement> ParsingTree { get; private set; }

        public SyntaxAnalyzer(ILexical lexer, Report report)
        {
            this.lexer = lexer;
            this.report = report;
        }

        public void ReadInstructions()
        {
            ParsingTree = new List<SelectStatement>();
            ReadUnionSelect();
        }

        private void ReadUnionSelect()
        {
            SelectStatement selInst = null;
            lexer.Token();
            while (lexer.TokenType == TokenTypeEnum.Select)
            {
                selInst = ReadSelectStatement();

                if (ParsingTree.Count > 0)
                    if (ParsingTree[ParsingTree.Count - 1].ActualColumnsCount != selInst.ActualColumnsCount) // TODO: May be I have to complicate the validation more than it is, to consider the name of the column.
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Columns count of two selects are inconsistant.");

                ParsingTree.Add(selInst);

                if (lexer.TokenType == TokenTypeEnum.Union)
                    lexer.Token();
            }

            if (lexer.TokenType != TokenTypeEnum.EOF)
                report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Unexpected char.");
        }

        private SelectStatement ReadSelectStatement()
        {
            ExpressionTerm tmpExp = null;
            SelectStatement selIns = new SelectStatement();

            if (lexer.TokenType == TokenTypeEnum.Select)
            {
                selIns.Columns = ReadColumnsList();
                selIns.PrepareActualColumns();

                if (lexer.TokenType == TokenTypeEnum.From)
                    selIns.From = ReadFromClause();
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "From");

                if (lexer.TokenType == TokenTypeEnum.Where)
                {
                    lexer.Token();
                    selIns.WhereExp = ReadCondition(ref tmpExp);
                }
            }
            else
                report.Error(lexer.Location, lexer.TokenType, lexer.Value, "SELECT");

            return selIns;
        }

        private FromClause ReadFromClause()
        {
            FromClause fromClause = new FromClause();
            JoinClause tmpJoin = null;
            ExpressionTerm tmpExp = null;

            fromClause.Source = ReadSourceList();
            fromClause.Sources.Add(fromClause.Source);

            while (lexer.TokenType.IsJoinType())
            {
                tmpJoin = new JoinClause();

                if (lexer.TokenType == TokenTypeEnum.Inner)
                    if (lexer.Token() == TokenTypeEnum.Join)
                        tmpJoin.JoiningType = JoiningTypeEnum.InnerJoin;
                    else
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Join");
                else if (lexer.TokenType == TokenTypeEnum.Left)
                    if (lexer.Token() == TokenTypeEnum.Outer)
                    {
                        if (lexer.Token() == TokenTypeEnum.Join)
                            tmpJoin.JoiningType = JoiningTypeEnum.LeftOuterJoin;
                        else
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Join");
                    }
                    else
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Outer");
                else if (lexer.TokenType == TokenTypeEnum.Right)
                    if (lexer.Token() == TokenTypeEnum.Outer)
                    {
                        if (lexer.Token() == TokenTypeEnum.Join)
                            tmpJoin.JoiningType = JoiningTypeEnum.RightOuterJoin;
                        else
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Join");
                    }
                    else
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Outer");
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Inner Join", "Left Outer Join", "Right Outer Join");


                tmpJoin.Source = ReadSourceList();
                fromClause.Sources.Add(tmpJoin.Source);

                if (lexer.TokenType == TokenTypeEnum.On)
                {
                    lexer.Token();
                    tmpJoin.LinkageExp = ReadCondition(ref tmpExp);
                }

                fromClause.Joins.Add(tmpJoin);
            }

            return fromClause;
        }

        private SourceList ReadSourceList()
        {
            SourceList tmpSourceList = new SourceList();

            do
            {
                if (lexer.Token() == TokenTypeEnum.String)
                {
                    if (!tmpSourceList.AddPath(lexer.Value.ToString()))
                    {
                        //TODO: I have to check if the addpath doesn't work correctly.
                    }
                    lexer.Token();
                }
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "string path of page or file.");

            } while (lexer.TokenType == TokenTypeEnum.Comma);

            if (lexer.TokenType == TokenTypeEnum.As)
                if (lexer.Token() == TokenTypeEnum.Identifier)
                {
                    tmpSourceList.Alias = lexer.Value.ToString(); // TODO: I think I have to check for the SourceList.Alias identity.
                    lexer.Token();
                }
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "alias");

            return tmpSourceList;
        }

        private List<Column> ReadColumnsList()
        {
            List<Column> retVal = new List<Column>();
            Column tmpCol = null;
            do
            {
                tmpCol = new Column();
                do
                {
                    lexer.Token();

                    if (lexer.TokenType == TokenTypeEnum.Identifier)
                    {
                        tmpCol.AppendColumnPath(lexer.Value);
                        lexer.Token();
                    }

                    if (lexer.TokenType == TokenTypeEnum.OpenBarBrackets)
                    {
                        tmpCol.AppendColumnPath(lexer.Value);
                        if (lexer.Token() == TokenTypeEnum.Number)
                            tmpCol.AppendColumnPath(lexer.Value);
                        else
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, "number");

                        if (lexer.Token() == TokenTypeEnum.CloseBarBrackets)
                            tmpCol.AppendColumnPath(lexer.Value);
                        else
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, "]");

                        lexer.Token();
                    }

                    if (lexer.TokenType.IsProperty())
                        tmpCol.Property = ReadProperty();

                    if (lexer.TokenType == TokenTypeEnum.As)
                    {
                        if (lexer.Token() == TokenTypeEnum.Identifier)
                        {
                            tmpCol.Alias = lexer.Value.ToString();
                            lexer.Token();

                            if (!lexer.TokenType.In(TokenTypeEnum.CloseParenthese, TokenTypeEnum.From, TokenTypeEnum.Comma))
                                report.Error(lexer.Location, lexer.TokenType, lexer.Value, "comma");

                            if (tmpCol.Property == null)
                                report.Error(lexer.Location, lexer.TokenType, lexer.Value, "property of the column");
                        }
                        else
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, "column name");
                    }

                    if (lexer.TokenType == TokenTypeEnum.OpenParentheses)
                    {
                        if (lexer.Token() != TokenTypeEnum.Dot)
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, ".");

                        tmpCol.ExtensionColumns = ReadColumnsList();

                        if (lexer.TokenType != TokenTypeEnum.CloseParenthese)
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, "}");

                        lexer.Token();
                    }
                } while (lexer.TokenType == TokenTypeEnum.Dot);
                //TODO: إذا وجدت فاصلة بنهاية الأعمدة فإنه ينشأ لها كائن فارغ مايؤدي إلى خطأ في زمن التشغيل
                if (!string.IsNullOrEmpty(tmpCol.Alias) || tmpCol.ExtensionColumns.Count != 0)
                    retVal.Add(tmpCol);
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "no coma");

            } while (lexer.TokenType == TokenTypeEnum.Comma);

            return retVal;
        }

        private PropertyMember ReadProperty()
        {
            PropertyMember tmpProp = new PropertyMember();
            tmpProp.Property = lexer.TokenType;

            if (lexer.TokenType.In(TokenTypeEnum.Text, TokenTypeEnum.ChildCount))
            {
                //lexer.Token();
                //return tmpProp;
            }
            else if (lexer.TokenType == TokenTypeEnum.GetAttribute)
            {
                if (lexer.Token() == TokenTypeEnum.OpenBrackets)
                    if (lexer.Token() == TokenTypeEnum.String)
                    {
                        tmpProp.PropertyParameter1 = lexer.Value.ToString();

                        if (lexer.Token() != TokenTypeEnum.CloseBrackets)
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, ")");
                    }
                    else
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, "name of attribute.");
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "(");

            }
            else if (lexer.TokenType == TokenTypeEnum.CountOf)
            {
                if (lexer.Token() == TokenTypeEnum.OpenBrackets)
                    if (lexer.Token() == TokenTypeEnum.String)
                    {
                        tmpProp.PropertyParameter1 = lexer.Value.ToString();

                        if (lexer.Token() != TokenTypeEnum.CloseBrackets)
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, ")");
                    }
                    else
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, "name of tag.");
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "(");
            }

            if (lexer.Token() == TokenTypeEnum.Dot)
            {
                lexer.Token();
                tmpProp.Function = ReadFunctionSequence();
            }
            return tmpProp;
        }

        private Function ReadFunctionSequence()
        {
            Function tmpFunc = new Function();
            tmpFunc.FunctionType = lexer.TokenType;

            ExpressionTerm lastExp = null;

            if (lexer.TokenType == TokenTypeEnum.SubString)
            {
                if (lexer.Token() == TokenTypeEnum.OpenBrackets)
                {
                    lexer.Token();
                    tmpFunc.StartIndexExp = ReadCondition(ref lastExp);

                    if (lexer.TokenType == TokenTypeEnum.Comma)
                    {
                        lexer.Token();
                        lastExp = null;
                        tmpFunc.LengthExp = ReadCondition(ref lastExp);

                        if (lexer.TokenType != TokenTypeEnum.CloseBrackets)
                            report.Error(lexer.Location, lexer.TokenType, lexer.Value, ")");
                    }
                    else if (lexer.TokenType != TokenTypeEnum.CloseBrackets)
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, ")");
                }
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "(");
            }
            else if (lexer.TokenType.In(TokenTypeEnum.Length, TokenTypeEnum.Trim))
            {
                if (lexer.Token() == TokenTypeEnum.OpenBrackets)
                {
                    if (lexer.Token() != TokenTypeEnum.CloseBrackets)
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, ")");
                }
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "(");
            }
            else if (lexer.TokenType.In(TokenTypeEnum.Like, TokenTypeEnum.IndexOf, TokenTypeEnum.Match))
            {
                if (lexer.Token() == TokenTypeEnum.OpenBrackets)
                {
                    lexer.Token();
                    lastExp = null;
                    tmpFunc.StringExp = ReadCondition(ref lastExp);

                    if (lexer.TokenType != TokenTypeEnum.CloseBrackets)
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, ")");
                }
            }
            else
                report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Function");

            if (lexer.Token() == TokenTypeEnum.Dot)
            {
                lexer.Token();
                tmpFunc.NextFunction = ReadFunctionSequence();
            }

            return tmpFunc;
        }


        #region Expresions
        private ExpressionTerm ReadCondition(ref ExpressionTerm last)
        {
            ExpressionTerm exp1 = ReadConditionTerm(ref last);
            ExpressionTerm exp2 = null;

            ExpressionTerm newExp = null;
            ExpressionTerm last2 = null;

            while (lexer.TokenType == TokenTypeEnum.OpOr)
            {
                newExp = new ExpressionTerm();
                newExp.TermType = lexer.TokenType;

                lexer.Token();
                exp2 = ReadConditionTerm(ref last2);

                last.Next = exp2;
                last2.Next = newExp;
                last = newExp;
            }

            return exp1;
        }

        private ExpressionTerm ReadConditionTerm(ref ExpressionTerm last)
        {
            ExpressionTerm exp1 = ReadConditionFact(ref last);
            ExpressionTerm exp2 = null;

            ExpressionTerm newExp = null;
            ExpressionTerm last2 = null;

            while (lexer.TokenType == TokenTypeEnum.OpAnd)
            {
                newExp = new ExpressionTerm();
                newExp.TermType = lexer.TokenType;

                lexer.Token();
                exp2 = ReadConditionFact(ref last2);

                last.Next = exp2;
                last2.Next = newExp;
                last = newExp;
            }

            return exp1;
        }

        private ExpressionTerm ReadConditionFact(ref ExpressionTerm last)
        {
            ExpressionTerm exp1 = ReadExpression(ref last);
            ExpressionTerm exp2 = null;

            ExpressionTerm newExp = null;
            ExpressionTerm last2 = null;

            if (lexer.TokenType.In(TokenTypeEnum.OpGreaterOrEqual, TokenTypeEnum.OpGreater, TokenTypeEnum.OpLessOrEqual, TokenTypeEnum.OpLess, TokenTypeEnum.OpEqualEqual, TokenTypeEnum.OpNotEqual))
            {
                newExp = new ExpressionTerm();
                newExp.TermType = lexer.TokenType;

                lexer.Token();
                exp2 = ReadExpression(ref last2);

                last.Next = exp2;
                last2.Next = newExp;
                last = newExp;
            }

            return exp1;
        }

        private ExpressionTerm ReadExpression(ref ExpressionTerm last)
        {
            ExpressionTerm exp1 = ReadExpressionTerm(ref last); // left term
            ExpressionTerm exp2 = null;               // right term

            ExpressionTerm newExp = null;             // mid operater
            ExpressionTerm last2 = null;

            while (lexer.TokenType.In(TokenTypeEnum.OpPlus, TokenTypeEnum.OpMinus))
            {
                newExp = new ExpressionTerm();
                newExp.TermType = lexer.TokenType;

                lexer.Token();
                exp2 = ReadExpressionTerm(ref last2);

                last.Next = exp2;
                last2.Next = newExp;
                last = newExp;
            }

            return exp1;
        }

        private ExpressionTerm ReadExpressionTerm(ref ExpressionTerm last)
        {
            ExpressionTerm exp1 = ReadExpressionFactor(ref last);
            ExpressionTerm exp2 = null;

            ExpressionTerm newExp = null;

            ExpressionTerm last2 = null;

            while (lexer.TokenType.In(TokenTypeEnum.OpMultiple, TokenTypeEnum.OpDivide))
            {
                newExp = new ExpressionTerm();
                newExp.TermType = lexer.TokenType;

                lexer.Token();
                exp2 = ReadExpressionFactor(ref last2);

                last.Next = exp2;
                last2.Next = newExp;
                last = newExp;
            }

            return exp1;
        }

        private ExpressionTerm ReadExpressionFactor(ref ExpressionTerm last)
        {
            ExpressionTerm exp1 = ReadExpressionFact(ref last);
            ExpressionTerm exp2 = null;

            ExpressionTerm newExp = null;
            ExpressionTerm last2 = null;

            if (lexer.TokenType == TokenTypeEnum.OpPower)
            {
                newExp = new ExpressionTerm();
                newExp.TermType = lexer.TokenType;

                lexer.Token();
                exp2 = ReadExpressionFact(ref last2);

                last.Next = exp2;
                last2.Next = newExp;
                last = newExp;
            }

            return exp1;
        }

        private ExpressionTerm ReadExpressionFact(ref ExpressionTerm last)
        {
            ExpressionTerm newExp = null;

            if (lexer.TokenType.In(TokenTypeEnum.Number, TokenTypeEnum.String))
            {
                newExp = new ExpressionTerm();
                newExp.TermType = lexer.TokenType;
                newExp.Value = lexer.Value;

                last = newExp;

                lexer.Token();

                return newExp;
            }
            else if (lexer.TokenType == TokenTypeEnum.OpNot)
            {
                newExp = new ExpressionTerm();
                newExp.TermType = lexer.TokenType;

                lexer.Token();

                ExpressionTerm exp2 = ReadExpressionFact(ref last);
                last.Next = newExp;
                last = newExp;

                return exp2;
            }
            else if (lexer.TokenType == TokenTypeEnum.OpenBrackets)
            {
                lexer.Token();

                newExp = ReadCondition(ref last);

                if (lexer.TokenType != TokenTypeEnum.CloseBrackets)
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, ")");

                lexer.Token();

                return newExp;
            }
            else if (lexer.TokenType == TokenTypeEnum.Identifier)
            {
                newExp = new ExpressionTerm();
                newExp.TermType = TokenTypeEnum.Term;
                newExp.Term = new Term();

                newExp.Term.AddIdentifier(lexer.Value.ToString());

                if (lexer.Token() != TokenTypeEnum.Dot)
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, ".");

                lexer.Token();
                if (lexer.TokenType.IsFunction())
                {
                    newExp.Term.Property = new PropertyMember();
                    newExp.Term.Property.Function = ReadFunctionSequence();
                }
                else if (lexer.TokenType == TokenTypeEnum.Identifier)
                {
                    newExp.Term.AddIdentifier(lexer.Value.ToString());
                    if (lexer.Token() == TokenTypeEnum.Dot)
                        lexer.Token();
                    else
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, ". followed by Property");

                    if (lexer.TokenType.IsProperty())
                    {
                        newExp.Term.Property = ReadProperty();
                        if (lexer.TokenType == TokenTypeEnum.Dot)
                        {
                            lexer.Token();
                            newExp.Term.Property.Function = ReadFunctionSequence();
                        }
                    }
                    else if (lexer.TokenType.IsFunction())
                        report.Error(lexer.Location, lexer.TokenType, lexer.Value, "property");
                    else
                        lexer.Token();
                }
                else
                    report.Error(lexer.Location, lexer.TokenType, lexer.Value, "Identifier or function");

                last = newExp;
                return newExp;
            }
            else
                report.Error(lexer.Location, lexer.TokenType, lexer.Value, "End of the ReadExpressionFact method.");

            return null;
        }
        #endregion
    }
}
